import axios, {
  type AxiosInstance,
  type AxiosResponse,
  type InternalAxiosRequestConfig,
} from "axios";
import type {
  DynamicallyFifterForCode,
  OnResponseDate,
  PrameForUseHttpCilent,
  RequestConfigOptions,
} from "~/modules/type.resquest";

/**
 * @param {string} timeoutErrorMessage The error message to display when a request timeout occurs
 * @param {RequestConfigOptions} options The options to be passed to the request config
 * @returns {RequestConfigOptions} The request config options object with the timeout error message
 * and the options to be passed to the request config constructor function when the timeout occurs and the options to be passed to the request config constructor function
 * @since version 0.0.1 <3441292862@qq.com>QingFeng
 * @summary The timeout error message and the options to be passed to the request config constructor function that returns a axios object containing
 */
const useAxiosConfig = (
  timeoutErrorMessage: string,
  options: RequestConfigOptions
) => {
  // add Dynamic request config to request config object if available in request config object
  // otherwise default config will be used when the timeout occurs and the options to be passed
  return DynamicAxiosManager(
    // create request config object if not available in request config object
    // otherwise default config will be used when the timeout occurs and the options to be passed
    axios.create({
      baseURL: options.baseUrl as string,
      timeout: options.timeOut as number,
      timeoutErrorMessage: timeoutErrorMessage,
    }),
    // add the options to be passed to the server
    options
  );
};

/**
 * The method of dynamically adding request headers, if the relevant key exists after searching for the relevant storage object,
 * then directly add the value of the specified key search in the relevant request header
 * @param {InternalAxiosRequestConfig} configAxios request parameter data configuration, we need to add the request header we want to add here
 * @param {Storage} storage To search for the storage object, we can use objects that inherit from Storage, such as {sessionStorage} or {localStorage} objects
 * @param {RequestConfig Options} options related configurations
 * @returns {Promise object} Promise resolved when successful or rejected when rejected with error object
 * @since version 0.0.1 <3441292862@qq.com>QingFeng
 * @summary The method of dynamically adding request headers, if the relevant key exists after searching for the relevant storage object,
 * then directly add the value of the specified key search in the relevant request header
 */
const DynamicAxiosManagerAddHeaders = (
  configAxios: InternalAxiosRequestConfig<unknown>,
  storage: Storage,
  options: RequestConfigOptions
): InternalAxiosRequestConfig<unknown> => {
  // This is the core method of dynamically searching for the key, which searches for the value of tokenKey in the storage object.
  // If it cannot be found, it will default to an empty string
  const token = (storage.getItem(options.tokenKey) as string) || "";
  if (token.length > 0) {
    // If the token is found, it adds the token to the request header
    configAxios.headers.Authorization = `${options.tokenKeyPrefix}${token}`;
  }
  return configAxios;
};

/**
 * The ` getOnResponseError ` function is used to handle situations where errors occur in the response data.
 * It is a generic function that receives a property mapping object 'propMap' (with the key being the response code and the value being a function that receives the response data object and returns a 'Promise', the result of which can be the processed response data object or a string type error message) and the actual response data object 'data'.
 * If there is no processing function corresponding to 'data. code' in 'propMap' (judged as' void 0 'through' isEqual '), a rejected' Promise 'is returned, indicating that the response data is invalid and accompanied by a response code;
 * Otherwise, call the processing function corresponding to the response code in propMap to process the response data and return the corresponding Promise result.
 * @param propMap Mapping object between response code and processing function
 * @param data Actual response data object
 * @returns Return a Promise, whose result is determined by the processing logic in propMap, which may be the processed response data or an error message.
 */
const getOnResponseError = <T extends unknown, V extends OnResponseDate<T>>(
  propMap: DynamicallyFifterForCode<T, V>,
  data: V
) => {
  if (isEqual(propMap[data.code], void 0)) {
    throw new Error(`Invalid response date:${data.code}`);
  }
  return propMap[data.code]!(data);
};

/**
 * Dynamically adding response interceptors, we classify data through data.code before returning it
 * @param {AxiosResponse<unknown, unknown>} data The response data from the server client side
 * @returns response object containing the response data from the server side
 */
const DynamicAxiosManagerAddResponseFunction = (
  data: AxiosResponse<unknown, unknown>
) => {
  // Perform data conversion to facilitate subsequent processing
  const response: OnResponseDate<unknown> = toRaw(
    data.data
  ) as OnResponseDate<unknown>;
  return getOnResponseError(
    {
      "2000": (data: OnResponseDate<unknown>) => Promise.resolve(data.data),
      "4000": (data: OnResponseDate<unknown>) => {
        throw new Error(data.msg);
      },
    },
    response
  ) as unknown as AxiosResponse<unknown, unknown>;
};

/**
 *@param {AxiosIstance} axios An instantiated axios object
 *@param {RequestConfigOptions} options Request configuration options object
 *@returns Set and add interceptors for responses
 *@since version 0.0.1 < 3441292862@qq.com >QingFeng
 *@summary: An Axios request wrapper used to add related interceptors,
 * Axios.interpeptors.request.use used to add request interceptors,
 * Axios.interpeptors.respons.use used to add response interceptors
 */
const DynamicAxiosManager = (
  axios: AxiosInstance,
  options: RequestConfigOptions
) => {
  // intercept request handlers for request handlers that are not supported by the server side and are not supported by the client side
  axios.interceptors.request.use(
    // intercept request handlers for request handlers that are not supported by the server side and
    // @this {DynamicAxiosManagerAddHeaders} function method is not supported by the server side
    (config) => DynamicAxiosManagerAddHeaders(config, localStorage, options),
    // add headers that are not supported by the server side and are not supported by the client side
    // @this {Promise} function is not supported by the client side
    (error) => Promise.reject(error)
  );
  // intercept response handlers for response handlers that are not supported by the server side and are not supported by the client side
  axios.interceptors.response.use(
    // intercept response handlers for response handlers that are not supported by the server side
    // @this {DynamicAxiosManagerAddResponseFunction} to handle response handlers that are not supported by the server side
    (response) => DynamicAxiosManagerAddResponseFunction(response),
    // add response handlers that are not supported by the server side and are not supported by the client side
    // @this {Promise} function is not supported by the client side
    (error) => Promise.reject(error)
  );
  // return axios object with properties of the server side
  return axios;
};

/**
 * @param {PrameForUseHttpCilent} prame for use with the server side configuration settings for the client side
 * @return Promise resolved with the server side configuration settings object for the client side configuration
 * @since version 0.0.1 < 3441292862@qq.com >QingFeng
 * @summary The central processing method for requests is to first obtain the relevant configuration items in the configuration.
 * If they cannot be found, the default values of the relevant data are used. Later, we assign this value to a {RequestConfig Options} object object object
 * Finally, we use the {useAxiosConfig function method} to initialize and add relevant configurations, as well as add response and request interceptors before we send the request.
 * After completing this step, we will return this object,Finally, we can happily use the processed data request related URL and use our axios.request ({config}) method to request data.
 * We can add our error handling here or add it outside
 */
export const useHttpCilent = async (prame: PrameForUseHttpCilent) => {
  // first: get the request config,get config in useRuntimeConfig().public.{configKey}
  const config = {
    baseUrl: useRuntimeConfig().public.baseURL || "http://localhost/api/",
    timeOut: useRuntimeConfig().public.timeOut || 3000,
    tokenKey: useRuntimeConfig().public.tokenKey || "userAgent-token",
    tokenKeyPrefix: useRuntimeConfig().public.tokenKeyPrefix || "Bearer",
  } as unknown as RequestConfigOptions;
  // second: initialize axios and add interceptors
  return await useAxiosConfig(
    prame.timeoutErrorMessage || "timeout error",
    config
  ).request({
    params: prame.params,
    data: prame.data,
    method: prame.method,
    url: prame.url,
  });
};

/**
 * @param {string} key is a unique key used in the way an API is processed
 * @param {Promise<AxiosResponse<undefined, undefined>>} response is a processing function for a repo request The middleware method for  data,
 * @returns where we use the useAsynchronous Data function supported by Nuxt
 * @example await useHttpCilentAsyncDataials("data", useHttpCilent({url: "226471515",method: 'GET'}))
 * @see https://nuxt.com/docs/api/composables/use-async-data nuxt.com for more information
 * @see https://nuxt.com/docs/ nuxt.com for more information
 * @since version 0.0.1 < 3441292862@qq.com >QingFeng
 */
export const useHttpCilentAsyncDataials = async (
  key: string,
  response: Promise<AxiosResponse<undefined, undefined>>
) => {
  return await useAsyncData(key, () =>
    response.catch((err: Error) => {
      console.log(err.message);
    })
  );
};

/**
 * @param {string} key is a unique key used in the way an API is processed
 * @param {Promise<AxiosResponse<undefined, undefined>>} response is a processing function for a repo request The middleware method for  data,
 * @returns where we use the useAsynchronous Data function supported by Nuxt
 * @example await useHttpCilentAsyncLazyDataials("data", useHttpCilent({url: "226471515",method: 'GET'}))
 * @see https://nuxt.com/docs/api/composables/use-async-data nuxt.com for more information
 * @see https://nuxt.com/docs/ nuxt.com for more information
 * @since version 0.0.1 < 3441292862@qq.com >QingFeng
 */
export const useHttpCilentAsyncLazyDataials = async (
  key: string,
  response: Promise<AxiosResponse<undefined, undefined>>
) => {
  return await useLazyAsyncData(key, () =>
    response.catch((err: Error) => {
      console.log(err.message);
    })
  );
};
